package com.alex.utils.textsearch;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.StringEscapeUtils;

import com.alex.utils.text.TextUtils;

public class HtmlTextExtractor {

	private static final String[] SENTENCE_BREAKING_TAGS = new String[] { "li", "p", "h1", "h2", "h3", "h4", "h5", "h6", "div", "table",
			"td", "tr", "th", "br", "img", "select", "option", "input", "button", "blockquote", "form", "dl", "ol", "ul", "hr", "noscript",
			"pre", "dd", "tbody", "tfoot", "thead", "object" };

	private static final Set<String> SENTENCE_BREAKING_TAGS_SET = new HashSet<String>();

	static {
		for (String tag : SENTENCE_BREAKING_TAGS) {
			SENTENCE_BREAKING_TAGS_SET.add(tag);
			SENTENCE_BREAKING_TAGS_SET.add("/" + tag);
		}
	}
	
	private Collection<SentenceProcessor> sentenceProcessors; 
	
	private Collection<SentenceFilter> sentenceFilters;

	private boolean body;

	private boolean script;

	private int position;

	private int lastTagEnd;

	private boolean firstLetter;

	private StringBuilder sentence;

	private boolean skipSentence;

	private boolean endOfSentence;

	private StringBuilder result;

	public void setSentenceFilters(Collection<SentenceFilter> sentenceFilters) {
		this.sentenceFilters = sentenceFilters;
	}

	public void setSentenceProcessors(Collection<SentenceProcessor> sentenceProcessors) {
		this.sentenceProcessors = sentenceProcessors;
	}
	
	public String extract(String html) {
		lastTagEnd = 0;
		position = 0;
		body = false;
		script = false;
		firstLetter = true;
		skipSentence = false;
		endOfSentence = false;
		result = new StringBuilder();
		sentence = new StringBuilder(30);

		while (position != html.length()) {
			char ch = html.charAt(position);

			if (ch == '<') {
				if (body && !script) {
					extractText(html, lastTagEnd, position);
				}

				position++;
				int tagEnd = html.indexOf('>', position);
				if (tagEnd < 0) {
					break;
				}
				String tag = extractTag(html, position, tagEnd);
				if ("body".equals(tag)) {
					body = true;
				}

				if ("script".equals(tag)) {
					script = true;
				}

				if ("/script".equals(tag)) {
					script = false;
				}

				if ("/body".equals(tag)) {
					break;
				}

				if (SENTENCE_BREAKING_TAGS_SET.contains(tag)) {
					newSentence();
				}

				position = tagEnd;
				lastTagEnd = tagEnd + 1;
			}

			position++;
		}
		return result.toString();
	}

	private void extractText(String html, int start, int end) {
		String text = StringEscapeUtils.unescapeHtml(html.substring(start, end));

		int i = 0;
		while (i < text.length()) {
			char ch = text.charAt(i);
			if (Character.isWhitespace(ch)) {
				ch = ' ';
			}

			if (firstLetter) {
				if (Character.isLetter(ch)) {
					if (!Character.isUpperCase(ch)) {
						skipSentence = true;
					}
					firstLetter = false;
				}
			}

			if (TextUtils.isEndOfSentence(ch)) {
				endOfSentence = true;
				do {
					if (!skipSentence) {
						sentence.append(ch);
					}
					i++;
				} while (i < text.length() && TextUtils.isEndOfSentence(ch = text.charAt(i)));
				newSentence();
			} else {
				if (!skipSentence) {
					sentence.append(ch);
				}
				i++;
			}
		}
	}

	private void newSentence() {
		if (sentence.length() != 0) {
			if (endOfSentence && !skipSentence) {
				String text = sentence.toString();
				text = applyProcessors(text).trim();
				if (applyFilters(text)) {
					result.append(text).append('\n');
				}
			}
		}

		sentence.setLength(0);
		firstLetter = true;
		skipSentence = false;
		endOfSentence = false;
	}

	private String applyProcessors(String text) {
		if (sentenceProcessors != null) {
			for (SentenceProcessor processor : sentenceProcessors) {
				text = processor.process(text);
			}
		}
		return text;
	}
	
	private boolean applyFilters(String text) {
		if (sentenceFilters != null) {
			for (SentenceFilter filter : sentenceFilters) {
				if (!filter.filter(text)) {
					return false;
				}
			}
		}
		return true;
	}

	private String extractTag(String html, int start, int end) {
		String tag = html.substring(start, end);
		int p = tag.indexOf(' ');
		if (p >= 0) {
			tag = tag.substring(0, p);
		}
		return tag.toLowerCase();
	}

}
